

namespace imichaelmiers.wp7.security.cryptography.PasswordProtectedStorage
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Runtime.Serialization;
    using System.Security.Cryptography;

    /// <summary>
    /// READ REMARKS SECTION BEFORE USING                            
    /// <para> Singleton persistent key-value store of of objects protected using 
    /// PasswordProtectedContainer<see cref="PasswordProtectedContainer{T}"/>
    /// </para>
    /// <para> The dictionary tags(keys)are public but authenticated. They cannot be
    /// tampered without the password, but can be read by anyone without 
    /// the password.</para>
    /// <para> Call save to persist data.</para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// RECOMMENDATIONS / LIMITATIONS. READ BEFORE USING :
    /// </para>
    /// <para>
    /// 1) USE STRONG PASSWORDS
    /// </para>
    /// <para>
    /// This is only as good as the strength of the password. Given a bad 
    /// password, it can be broken on a 486 in a couple of minutes. 
    /// Enforcing minimum password complexity requirements is recommended.
    /// </para>
    /// <para>
    /// 2) Unavoidably, objects encrypted under the same password and stored
    /// with the same tag are substitutable with each-other. 
    /// </para>
    /// <para>
    /// This means an attacker can substitute old version of an object with 
    /// new ones and no errors will be raised. If this is an issue,use 
    /// distinct passwords or do some checking to make sure you got back the 
    /// right object.
    /// </para>
    /// <para>
    /// It is strongly recommended that you use this class rather the raw 
    /// PasswordProtectedContainer. First, it is just easier as it 
    /// handles storage for you.
    /// </para>
    /// <para>
    /// Second, it mitigates the possibility of someone substituting a 
    /// different object protected under the same
    /// password. Were you to just use PasswordProtectedContainer and 
    /// serialize them to disk, one could easily replace that object with 
    /// a different object that was protected by the same password
    /// and you would have no way of knowing this. 
    /// </para>
    /// <para>
    /// In the case of the PasswordProtectedStore, however, there is at least an
    /// identifying tag associated with the object so the most someone can do 
    /// is hand you something that was previously stored under that tag and 
    /// password. This can be a serious vulnerability for an application, but
    /// preventing it requires application specific knowledge of what a 
    /// stale/old  object is.
    /// </para>
    /// <para>
    /// 3)Don't split data that needs to be authenticated across multiple 
    /// objects. 
    /// </para>
    /// <para>
    /// Splitting data  will result in spliced together data.
    /// Consider a set of bank transactions stored as
    /// <code>
    ///    sealedObject(Name1,password1),sealedObject(amount,password2)
    ///    sealedObject(Name2,password1),sealedObject(largerAmount,password2)
    /// </code>
    /// The following is a valid transaction even though its malicious: 
    ///    <code>sealedObject(Name1,password1),sealedObject(largerAmount,password2)</code>
    /// </para>
    /// <para>
    /// 4) Always ask the user for the password, don't store it.
    /// </para>
    /// <para>
    /// passwords can be read out of even compiled code and 
    /// there is no secure location to store them on Windows phone, 
    /// hence the need for this code
    /// </para>
    /// <para>
    /// 5) Do not store passwords in strings, use mutable arrays and
    ///  delete them when done.
    /// </para>
    /// <para>
    /// Strings in .net are not mutable and cannot be erased. 
    /// </para>
    /// <para>
    /// As a result, its possible anyone who gets physical access to the
    /// device running this code(after it has executed) to recover 
    /// password. Given that memory can get swapped to disk, this holds
    /// even if the device is powered off later. At that point reading 
    /// and modifying the data is trivial.
    /// </para>
    /// <para>
    /// This is why System.Security.SecureString exists. Unfortunately,
    /// it is not supported in Silverlight.
    /// </para>
    /// <para>
    /// It is recommended you store the password in a byte array, never 
    /// ever write any of it to a string, and erase the byte array
    /// </para>
    /// <para>
    /// Using a byte array is the preferred means to store the password.
    /// For this to be useful it is important that the password is never 
    /// stored in a string. This does not solve the problem of the password
    /// being swapped out to disk, but it minimizes the time in which it
    /// can happen. Now it has to happen after the password has been read
    /// and before it is zeroed instead of sometime after your code has
    /// exited but before the gc ran and then the memory was overwritten.
    /// </para>
    /// <para>
    /// If reading in from the command line, use the the ReadKey method and 
    /// read a key at a time into a byte array. If reading from a text box, 
    /// capture the key events and pull the last character each time, place 
    /// it into the byte array, and then overwrite the character in the
    /// text box with *. Make sure to handle corner cases such as delete 
    /// or the cursor moving.
    /// </para>
    /// <para>
    /// When done with the password, make sure to overwrite it.  
    /// Array.Clear(YourPasswordByteArray,0,YourPasswordByteArray.Length) 
    /// should work.  
    /// YourPAsswordByteArray = new byte[length]  DOES NOT WORK.
    /// </para>
    /// </remarks>  
    public sealed class PasswordProtectedStore
    {
        /// <summary>
        /// The name of the file the store is serialized to.
        /// </summary>
        public const string Id = "84731650-c296-43c1-950a-d891be51db67";

        private static readonly PasswordProtectedStore instance =
            new PasswordProtectedStore();

        private readonly Dictionary<string, PasswordProtectedContainer<KeyValuePair>> dict;

        private DataContractSerializer serializer;

        /// <summary>
        /// The instance of the singleton. 
        /// </summary>
        public static PasswordProtectedStore Store { get { return instance; } }

        /// <summary>
        /// <see cref="System.Collections.Generic.Dictionary.Count"/>
        /// </summary>
        public int Count { get { return dict.Count; } }

        /// <summary>
        /// <see cref="System.Collections.Generic.IDictionary.Keys"/>
        /// </summary>
        public ICollection<string> Keys { get { return dict.Keys; } }

        private PasswordProtectedStore()
        {
            serializer = new DataContractSerializer(typeof(Dictionary<string, PasswordProtectedContainer<KeyValuePair>>));

            using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (isoStore.FileExists(PasswordProtectedStore.Id))
                {
                    using (IsolatedStorageFileStream file = 
                        new IsolatedStorageFileStream(PasswordProtectedStore.Id, FileMode.Open, isoStore))
                    {
                        dict = (Dictionary<string, PasswordProtectedContainer<KeyValuePair>>)serializer.ReadObject(file);
                    }
                }
                else
                { 
                    // We need to create the store if it does not already exist.
                    (new IsolatedStorageFileStream(PasswordProtectedStore.Id, FileMode.Create, isoStore)).Close();
                    dict = new Dictionary<string, PasswordProtectedContainer<KeyValuePair>>();
                    this.Save();
                }
            }
        }

        /// <summary>
        /// Adds the data to the store under the provided key  and protects 
        /// it under the given password.
        /// </summary>
        /// <param name="password">The password to protect the data with</param>
        /// <param name="key"> The dictionary key the data mapped by</param>
        /// <param name="value">The value to store</param>
        /// <exception cref="ArgumentException"> If there already is a key stored under that value.</exception>
        public void Add(byte[] password, string key, object value)
        {
            Add(key, new PasswordProtectedContainer<KeyValuePair>(password, new KeyValuePair(key, value)));
        }

        /// <summary>
        /// Adds the data to the store and protects it under the given password. 
        /// <para>  WARNING don't use strings to store passwords    
        ///  <see cref="PasswordProtectedStore"/> remarks warning 5 for why.</para>
        ///  <see cref="Add(byte[],string,object)"/>
        /// </summary>
        public void Add(string password, string key, object value)
        {
            Add(key, new PasswordProtectedContainer<KeyValuePair>(password, new KeyValuePair(key, value)));
        }

        /// <summary>
        /// Clears the stored data.
        /// </summary>
        public void Clear()
        {
            dict.Clear();
        }

        /// <summary>
        /// Gets the object in the store protected by the given password.
        /// </summary>
        /// <exception cref="CryptographicException">
        /// If the password is wrong or the data was tampered with.</exception>
        /// <param name="password"> The password for the data.</param>
        /// <param name="key">The key the data is mapped to.</param>
        /// <returns>The object</returns>
        public object Get(byte[] password, string key)
        {
            PasswordProtectedContainer<KeyValuePair> pair = dict[key];
            return Verify(key, pair.GetData(password));
        }

        /// <summary>
        /// Gets the object in the store protected by the given password.
        /// <para>  WARNING don't use strings to store passwords    
        ///  <see cref="PasswordProtectedStore"/> remarks warning 5 for why.</para>
        /// <see cref="Get(byte[],string)"/>
        /// </summary>
        public object Get(string password, string key)
        {
            PasswordProtectedContainer<KeyValuePair> pair = dict[key];

           return Verify(key, pair.GetData(password));
        }

        /// <summary>
        /// Deletes the data mapped to the given key from the store.
        /// </summary>
        /// <param name="key"> the key to delete.</param>
        public void Remove(string key)
        {
            dict.Remove(key);
        }

        /// <summary>
        /// Saves the key value store to isolated storage. 
        /// Call to persist modifications.
        /// </summary>
        public void Save()
        {
            using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream file =  
                    new IsolatedStorageFileStream(Id, FileMode.Open, FileAccess.ReadWrite, isoStore))
                {
                    serializer.WriteObject(file, dict);
                }
            }
        }

        // Silverlight throws an ArgumentException with a value out of range message on duplicate
        // key insert. This throws a more accurate exceptions.
        private void Add(string key, PasswordProtectedContainer<KeyValuePair> value)
        {
            if (dict.ContainsKey(key))
            {
                throw new ArgumentException(String.Format("Data already stored under key {0}.", key));
            }
            dict.Add(key, value);
        }
        private object Verify(string key, KeyValuePair decrypt)
        {
            if (decrypt.Key == key)
            {
                return decrypt.Value;
            }
            else
            {
                throw new CryptographicException(String.Format(
                    "The key value store has been tampered with. Expected" +
                    "object keyed by {0} but got one that was keyed by {1}",
                     key, 
                    decrypt.Key));
            }
        }
    }    
}
